#include <jni.h>
#include <malloc.h>
#include <android/log.h>
#include "mediakit.h"
#include "Util/logger.h"
#include "Util/onceToken.h"
#include "Network/sockutil.h"
#include "Common/config.h"
#include "MvManager.h"

using namespace std;
using namespace ZL::Util;
using namespace ZL::Network;

class AndroidLogoutChannel : public LogChannel {
public:
    AndroidLogoutChannel(const string &name, LogLevel level = LDebug) : LogChannel(name, level) {}

    virtual ~AndroidLogoutChannel() {}

    void write(const LogInfoPtr &logInfo) {
        if (level() > logInfo->_level) {
            return;
        }
        static android_LogPriority LogPriorityArr[10];
        static onceToken s_token([]() {
            LogPriorityArr[LTrace] = ANDROID_LOG_VERBOSE;
            LogPriorityArr[LDebug] = ANDROID_LOG_DEBUG;
            LogPriorityArr[LInfo] = ANDROID_LOG_INFO;
            LogPriorityArr[LWarn] = ANDROID_LOG_WARN;
            LogPriorityArr[LError] = ANDROID_LOG_ERROR;
        }, nullptr);
        __android_log_print(LogPriorityArr[logInfo->_level], "MEDIA_JNI", "%s %d",
                            logInfo->_file.c_str(), logInfo->_line);
        __android_log_print(LogPriorityArr[logInfo->_level], "MEDIA_JNI", "%s %s",
                            logInfo->_function.c_str(), logInfo->_message.str().c_str());
    }
};

/*
 * 加载动态库
 */
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    onAppStart();
    Logger::Instance().add(std::make_shared<AndroidLogoutChannel>("android", LTrace));
    Logger::Instance().setWriter(std::make_shared<AsyncLogWriter>());
    return JNI_VERSION_1_6;
}

/*
 * 释放动态库
 */
JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) {
    MvManager::Destory();
    onAppExit();
}

#define JNI_API(retType, funName, ...) extern "C"  JNIEXPORT retType Java_com_wing_tools_video_ZLMediaKit_##funName(JNIEnv* env, jclass cls,__VA_ARGS__)
#define JNI_API1(retType, funName) extern "C"  JNIEXPORT retType Java_com_wing_tools_video_ZLMediaKit_##funName(JNIEnv* env, jclass cls)

string jstringTostring(JNIEnv *env, jstring jstr) {
    char *rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "()[B");
    jbyteArray barr = (jbyteArray) env->CallObjectMethod(jstr, mid);
    jsize alen = env->GetArrayLength(barr);
    jbyte *ba = (jbyte *) env->GetByteArrayElements(barr, JNI_FALSE);
    ba[alen] = 0;
    string ret = (char *) ba;
    env->ReleaseByteArrayElements(barr, ba, 0);
    /*局部变量其实在函数返回后会自动释放*/ \
    env->DeleteLocalRef(clsstring);
    return ret;
}

unsigned char *
memfind(unsigned char *buf, unsigned long len, const char *subbuf, unsigned long sublen) {
    for (unsigned long i = 0; i < len - sublen; i++) {
        if (memcmp((char *) (buf + i), subbuf, sublen) == 0) {
            return buf + i;
        }
    }
    return NULL;
}

void HandleH264(unsigned char *pEncodeBuffer, unsigned long dwBufferLen,
                std::function<void(unsigned char *, unsigned long)> cb) {
    unsigned char *end, *start = pEncodeBuffer;
    for (; start < pEncodeBuffer + dwBufferLen - 3;) {
        if (memcmp((char *) start, "\x00\x00\x01", 3) == 0) {
            //one frame
            //start+=3;
            //find the end of frame
            end = memfind(start + 3, pEncodeBuffer + dwBufferLen - start - 3, "\x00\x00\x01", 3);
            if (end == NULL) {//this is the last frame
                //HandleH264_1(start,pEncodeBuffer+dwBufferLen-start,time);
                cb(start, pEncodeBuffer + dwBufferLen - start);
                return;
            } else {//finded the end of fame
                //HandleH264_1(start,end-start,time);
                cb(start, end - start);
                start = end;
                continue;
            }
        } else {
            start++;
            continue;
        }
    }
}

static uint16_t s_httpPort = 8080;
static uint16_t s_rtspPort = 8554;
static uint16_t s_rtmpPort = 1935;

JNI_API(jint, initRtspServer, jshort port) {
    DebugL << port;
    int ret;
    static onceToken s_token([&]() {
        ret = initRtspServer(port);
        s_rtspPort = ret;
    }, nullptr);
    return ret;
}

JNI_API(jint, initRtmpServer, jshort port) {
    DebugL << port;
    int ret;
    static onceToken s_token([&]() {
        ret = initRtmpServer(port);
        s_rtmpPort = ret;
    }, nullptr);
    return ret;
}

JNI_API(jint, initHttpServer, jshort port) {
    DebugL << port;
    int ret;
    static onceToken s_token([&]() {
        ret = initHttpServer(port);
        s_httpPort = ret;
    }, nullptr);
    return ret;
}

JNI_API(void, setGlobalOptionString, jstring key, jstring value) {
    DebugL << jstringTostring(env, key) << "=" << jstringTostring(env, value);
    setGlobalOptionString(jstringTostring(env, key).data(), jstringTostring(env, value).data());
    auto str_key = jstringTostring(env, key);
    if(str_key == Config::Http::kRootPath){
        auto filePath = jstringTostring(env, value) + "/" + DEFAULT_VHOST + "/log.log" ;
        auto filePtr = File::createfile_file(filePath.data(),"ab");
        if(filePtr) {
            fclose(filePtr);
            Logger::Instance().add(std::make_shared<FileChannel>("file", filePath, LTrace));
        }
    }
}

JNI_API1(jstring, getLocalIp) {
    return env->NewStringUTF(SockUtil::get_local_ip().data());
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    createMedia
 * Signature: (Ljava/lang/String;Ljava/lang/String;)J
 */
JNI_API(jlong, createMedia, jstring appName, jstring mediaName) {
    auto ctx = (jlong) createMedia(jstringTostring(env, appName).c_str(),
                                   jstringTostring(env, mediaName).c_str());
    DebugL << ctx << " " << jstringTostring(env, appName).c_str()
           << jstringTostring(env, mediaName).c_str();
    return ctx;
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    releaseMedia
 * Signature: (J)V
 */
JNI_API(void, releaseMedia, jlong ctx) {
    DebugL << ctx;
    MediaContext context = ((MediaContext) ctx);
    releaseMedia(context);
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    mediaInitVideo
 * Signature: (JIII)V
 */
JNI_API(void, mediaInitVideo, jlong ctx, jint width, jint height, jint rate) {
    DebugL << ctx << " " << width << " " << height << " " << rate;
    MediaContext context = ((MediaContext) ctx);
    media_initVideo(context, width, height, rate);
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    mediaInitAudio
 * Signature: (JIII)V
 */
JNI_API(void, mediaInitAudio, jlong ctx, jint channel, jint sampleBit, jint smapleRate) {
    DebugL << ctx << " " << channel << " " << sampleBit << " " << smapleRate;
    MediaContext context = ((MediaContext) ctx);
    media_initAudio(context, channel, sampleBit, smapleRate);
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    mediaInputH264
 * Signature: (J[BIJ)V
 */
JNI_API(void, mediaInputH264, jlong ctx, jbyteArray _buf, jint len, jlong stamp,
        jboolean allinone) {
    MediaContext context = ((MediaContext) ctx);
    jbyte *olddata = (jbyte *) env->GetByteArrayElements(_buf, JNI_FALSE);
    jsize oldsize = env->GetArrayLength(_buf);

    if (allinone) {
        HandleH264((unsigned char *) olddata, (unsigned long) oldsize,
                   [&](unsigned char *dt, unsigned long size) {
                       media_inputH264(context, dt, size, 0);
                   });
    } else {
        media_inputH264(context, olddata, oldsize, 0);
    }

    env->ReleaseByteArrayElements(_buf, olddata, 0);
}

/*
 * Class:     com_wing_tools_video_ZLMediaKit
 * Method:    mediaInputAAC
 * Signature: (J[BIJ)V
 */
JNI_API(void, mediaInputAAC, jlong ctx, jbyteArray _buf, jint len, jlong stamp) {
    MediaContext context = ((MediaContext) ctx);
    jbyte *olddata = (jbyte *) env->GetByteArrayElements(_buf, JNI_FALSE);
    jsize oldsize = env->GetArrayLength(_buf);
    media_inputAAC(context, olddata, oldsize, 0);
    env->ReleaseByteArrayElements(_buf, olddata, 0);
}

/*
 * 1.geturl函数
2.initXXXServer返回使用端口
3.码率估算接口
4.回调接口
 */


JNI_API(jstring, getUrl, jint type, jstring app, jstring stream) {
    char url[256] = {0};
    switch (type) {
        case 1://rtmp
            sprintf(url, "rtmp://%s:%d/%s/%s", SockUtil::get_local_ip().data(), s_rtmpPort,
                    jstringTostring(env, app).data(), jstringTostring(env, stream).data());
            break;
        case 2://hls
            sprintf(url, "http://%s:%d/%s/%s/hls.m3u8", SockUtil::get_local_ip().data(), s_httpPort,
                    jstringTostring(env, app).data(), jstringTostring(env, stream).data());
            break;
        case 3://http-flv
            sprintf(url, "http://%s:%d/%s/%s.flv", SockUtil::get_local_ip().data(), s_httpPort,
                    jstringTostring(env, app).data(), jstringTostring(env, stream).data());
            break;
        default://rtsp
            sprintf(url, "rtsp://%s:%d/%s/%s", SockUtil::get_local_ip().data(), s_rtspPort,
                    jstringTostring(env, app).data(), jstringTostring(env, stream).data());
            break;

    }
    return env->NewStringUTF(url);
}


JNI_API(jint, getBitRate, jint width, jint height, jint fps) {
    //一张1080P的照片大概为3MB，25 fps 大概是75MB
    //但是在4Mb/s的情况下已经是1080p比较好的画质了，
    //所以压缩率大概是 75 / (4 / 8) = 150 倍
    int yuvBytesPerSecond = width * height * 1.5 * fps;
    int bitRate = yuvBytesPerSecond / 150 * 8;
    return bitRate;
}

#include "Common/config.h"
#include "Network/TcpSession.h"
#include "Common/MediaSource.h"
#include <chrono>

using namespace Config;
using namespace ZL::Media;
using namespace ZL::Network;

static MediaInfo s_info;
static string s_peerIp;
static int s_eventType = 0;
static mutex s_mtx;
static condition_variable_any s_cond;

//等待事件
//返回事件类型
//该函数一直阻塞直到有事件为止
JNI_API(jint, waitForEvent, jint millisec) {
    static onceToken s_token([&]() {

        {
            //更新配置，设置汇报阈值为0；让kBroadcastFlowReport事件在每次播放器断开时触发
            mINI::Instance()[Broadcast::kFlowThreshold] = 0;
            NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
        }

        //监听播放器播放事件
        NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastMediaPlayed,
                                             [&](BroadcastMediaPlayedArgs) {
                                                 invoker("");//auth success
                                                 unique_lock<mutex> lck(s_mtx);
                                                 s_eventType = 0;
                                                 s_info = args;
                                                 s_peerIp = sender.get_peer_ip();
                                                 s_cond.notify_all();
                                             });
        //监听播放器断开流量汇报事件。
        NoticeCenter::Instance().addListener(nullptr, Broadcast::kBroadcastFlowReport,
                                             [&](BroadcastFlowReportArgs) {
                                                 unique_lock<mutex> lck(s_mtx);
                                                 s_eventType = 1;
                                                 s_info = args;
                                                 s_peerIp = sender.get_peer_ip();
                                                 s_cond.notify_all();
                                             });
    }, nullptr);

    unique_lock<mutex> lck(s_mtx);
    s_eventType = -1;
    if (millisec == 0) {
        s_cond.wait(lck);
    } else if (s_cond.wait_for(lck, std::chrono::milliseconds(millisec)) == std::cv_status::timeout) {
        //等待超时
        s_eventType = -1;
    }
    return s_eventType;
}

//立即停止事件等待
JNI_API1(void, stopEventWait) {
    unique_lock<mutex> lck(s_mtx);
    s_eventType = -1;
    s_cond.notify_all();
}
//返回事件详情
JNI_API(jstring, getEventInfo, jint opt) {
    unique_lock<mutex> lck(s_mtx);
    switch (opt) {
        case 0:
            return env->NewStringUTF(s_info.m_schema.data());
            break;
        case 1:
            return env->NewStringUTF(s_info.m_vhost.data());
            break;
        case 2:
            return env->NewStringUTF(s_info.m_app.data());
            break;
        case 3:
            return env->NewStringUTF(s_info.m_streamid.data());
            break;
        case 4:
            return env->NewStringUTF(s_info.m_param_strs.data());
            break;
        case 5:
            return env->NewStringUTF(s_peerIp.data());
            break;
        default:
            return env->NewStringUTF("");
            break;
    }
}




//广播更新配置
JNI_API1(void, reloadConfig) {
    NoticeCenter::Instance().emitEvent(Broadcast::kBroadcastReloadConfig);
}

//创建目录
JNI_API(void, createPath,jstring path) {
    File::createfile_path(jstringTostring(env,path).data(),664);
}


//创建目录
JNI_API1(void, startMvManager) {
    MvManager::Instance();
}

JNI_API(jlong, createProxyPlayer,jstring app,jstring stream,int type) {
    return (jlong)createProxyPlayer(jstringTostring(env,app).data(),jstringTostring(env,stream).data(),type);
}

JNI_API(void, releaseProxyPlayer,jlong ctx) {
    releaseProxyPlayer((ProxyPlayerContext)ctx);
}

JNI_API(void, proxyPlayerPlay,jlong ctx,jstring url) {
    proxyPlayer_play((ProxyPlayerContext)ctx,jstringTostring(env,url).data());
}






















